import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.regex.Pattern;

/**
 * {@code Analyzer} is a helper class for the compiler. Its task is to
 * remember type information as well as compiler options.
 */
public class Analyzer {

	// Map variables to types
	private HashMap<String, String> typeMap;
	// Map activities to operations used
	private HashMap<String, HashSet<String>> activityOperations;
	// The set of packages in the process class path
	private HashSet<String> packageList;
	// The set of activities used by the running process
	private HashSet<String> activitiesUsed;
	// The core activities of the process
	private HashSet<String> coreActivities;
	// The activities started in this process (and which do not need to be
	// directly imported
	private HashSet<String> activitiesStarted;

	public Analyzer() {
		this.activitiesUsed = new HashSet<String>();
		this.activitiesStarted = new HashSet<String>();
		this.coreActivities = new HashSet<String>();
		this.coreActivities.add("ActorManager");
		this.coreActivities.add("ProjectManager");
		this.coreActivities.add("ProcessManager");
		this.coreActivities.add("RoleManager");
		this.activityOperations = new HashMap<String, HashSet<String>>();
		typeMap = new HashMap<String, String>();
		this.packageList = new HashSet<String>();
		this.packageList.add("java.lang");
		this.packageList.add("java.util");
	}

	boolean isCoreActivity(String activity) {
		return this.coreActivities.contains(activity);
	}

	public void registerStartedActivity(String activity) {
		this.activitiesStarted.add(activity);
	}

	public String getTypeName(String varname, String expression) {
		if ( typeMap.keySet().contains(varname) ) {
			return "";
		}

		// Test for String
		if ( Pattern.matches("\".*?\"", expression) ) {
			typeMap.put(varname, "String");
			return "String";
		}

		// Test for new
		if ( Pattern.matches("new\\s+?.*?", expression) ) {
			String type = expression.substring(4, expression.indexOf('(')).trim();
			typeMap.put(varname, type);
			return type;
		}

		// Test for method call
		if ( Pattern.matches(".*?\\..*?", expression) ) {
			String var = expression.substring(0, expression.indexOf('.')).trim();
			String type;
			if ( var.startsWith("var_") )
				type = var.substring(4);
			else
				type = typeMap.get(var);
			String operation = expression.substring(expression.indexOf('.') + 1,
					expression.indexOf('('));
			// Handle special case of operation lookup
			if ( type.startsWith("Operation/") ) {
				operation = var;
				type = type.substring(10);
			}
			try {
				Class<?> clazz = lookUpClass(type);
				Method[] methods = clazz.getDeclaredMethods();
				for ( int i = 0; i < methods.length; i++ )
					if ( methods[i].getName().equals(operation) ) {
						type = methods[i].getReturnType().getCanonicalName().toString();
						typeMap.put(varname, type);
						return type;
					}
				typeMap.put(varname, type);
				return type;
			} catch (Exception exc) {
				exc.printStackTrace();
				return null;
			}
		}	
		return "[NOT YET IMPLEMENTED]";
	}

	String getRequiredCast(String type) {
		if ( type.equals("int") )
			return "(Integer)";
		if ( type.equals("long") )
			return "(Long)";
		if ( type.equals("char") )
			return "(Char)";
		if ( type.equals("short") )
			return "(Short)";
		if ( type.equals("byte") )
			return "(Byte)";
		if ( type.equals("boolean") )
			return "(Boolean)";
		return "(" + type + ")";
	}

	private Class<?> lookUpClass(String type) throws ClassNotFoundException {
		for ( String pkg : this.packageList )
			try {
				Class<?> clazz = Class.forName(pkg + "." + type);
				return clazz;
			} catch (ClassNotFoundException e) {}
			throw new ClassNotFoundException(type);
	}

	public void registerActivity(String activity, String operation) {
		this.activitiesUsed.add(activity);
		HashSet<String> set = this.activityOperations.get(activity);
		if ( set == null )
			set = new HashSet<String>();
		set.add(operation);
		this.typeMap.put(operation, "Operation/" + activity);
		this.activityOperations.put(activity, set);
	}

	/*
	 * All activities used but not started in the process must be looked-up
	 * in the process
	 */
	public String getActivityLookupDeclaration() {
		StringBuffer sb = new StringBuffer();
		for ( String activity : this.activitiesUsed )
			if ( !this.coreActivities.contains(activity) &&
					!this.activitiesStarted.contains(activity) )
				sb.append("\t\t" + activity + " var_" + activity + 
						" = (" + activity +
						")var_ActivityManager.lookup(\"var_" + activity
						+ "\");\n");
		for ( String activity : this.activityOperations.keySet() )
			for ( String operation : this.activityOperations.get(activity) )
				if ( !this.coreActivities.contains(activity) )
					sb.append("\t\tOperation " + operation +
							" = var_ActivityManager.getOperation(var_" 
							+ activity + ", \"" + operation + "\");\n");
		return sb.toString();
	}

	boolean isVerboseOption(String...args) {
		for ( String parameter : args )
			if ( parameter.equals("-v") )
				return true;
		return false;
	}

	String getInputFile(String...args) {
		return args[args.length-1];
	}

	void setClassPath(String...args) {
		for ( int i = 0 ; i < args.length - 1; i++ )
			if ( args[i].equals("-c") ) {
				String path = args[i+1];
				String[] packages = path.split(":");
				for ( String pkg : packages )
					this.packageList.add(pkg);
				return;
			}
	}

}